Passed
Pull Request — master (#136)
by
unknown
01:53
created

ID3Util.js ➔ getSpecOptions   A

Complexity

Conditions 2

Size

Total Lines 7
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 4
dl 0
loc 7
c 0
b 0
f 0
rs 10
cc 2
1
const iconv = require('iconv-lite')
2
import { ID3_FRAME_OPTIONS } from './definitions/FrameOptions'
3
4
const ENCODINGS = [
5
    'ISO-8859-1', 'UTF-16', 'UTF-16BE', 'UTF-8'
6
]
7
8
export class SplitBuffer {
9
    constructor(value = null, remainder = null) {
10
        this.value = value
11
        this.remainder = remainder
12
    }
13
}
14
15
export function splitNullTerminatedBuffer(buffer, encodingByte = 0x00) {
16
    const termination = { start: -1, size: 0 }
17
    if(encodingByte === 0x01 || encodingByte === 0x02) {
18
        termination.start = buffer.indexOf(Buffer.from([0x00, 0x00]))
19
        termination.size = 2
20
        if(termination.start !== -1 && buffer.length > (termination.start + termination.size)) {
21
            if(buffer[termination.start + termination.size] === 0x00) {
22
                termination.start += 1
23
            }
24
        }
25
    } else {
26
        termination.start = buffer.indexOf(0x00)
27
        termination.size = 1
28
    }
29
30
    if(termination.start === -1) {
31
        return new this.SplitBuffer(null, buffer.slice(0))
32
    }
33
    if(buffer.length <= termination.start + termination.length) {
34
        return new this.SplitBuffer(buffer.slice(0, termination.start), null)
35
    }
36
    return new this.SplitBuffer(buffer.slice(0, termination.start), buffer.slice(termination.start + termination.size))
37
}
38
39
export function encodingFromStringOrByte(encoding) {
40
    if(ENCODINGS.indexOf(encoding) !== -1) {
41
        return encoding
42
    } else if(encoding > -1 && encoding < ENCODINGS.length) {
43
        encoding = ENCODINGS[encoding]
44
    } else {
45
        encoding = ENCODINGS[0]
46
    }
47
    return encoding
48
}
49
50
export function stringToEncodedBuffer(str, encodingByte) {
51
    return iconv.encode(str, this.encodingFromStringOrByte(encodingByte))
52
}
53
54
export function bufferToDecodedString(buffer, encodingByte) {
55
    return iconv.decode(buffer, this.encodingFromStringOrByte(encodingByte)).replace(/\0/g, '')
56
}
57
58
export function getSpecOptions(frameIdentifier) {
59
    if (ID3_FRAME_OPTIONS[frameIdentifier]) {
60
        return ID3_FRAME_OPTIONS[frameIdentifier]
61
    }
62
63
    return {}
64
}
65
66
export function isValidID3Header(buffer) {
67
    if(buffer.length < 10) {
68
        return false
69
    }
70
    if(buffer.readUIntBE(0, 3) !== 0x494433) {
71
        return false
72
    }
73
    if([0x02, 0x03, 0x04].indexOf(buffer[3]) === -1 || buffer[4] !== 0x00) {
74
        return false
75
    }
76
    return this.isValidEncodedSize(buffer.slice(6, 10))
77
}
78
79
export function getFramePosition(buffer) {
80
    /* Search Buffer for valid ID3 frame */
81
    let framePosition = -1
82
    let frameHeaderValid = false
83
    do {
84
        framePosition = buffer.indexOf("ID3", framePosition + 1)
85
        if(framePosition !== -1) {
86
            /* It's possible that there is a "ID3" sequence without being an ID3 Frame,
87
             * so we need to check for validity of the next 10 bytes
88
             */
89
            frameHeaderValid = this.isValidID3Header(buffer.slice(framePosition, framePosition + 10))
90
        }
91
    } while (framePosition !== -1 && !frameHeaderValid)
92
93
    if(!frameHeaderValid) {
94
        return -1
95
    }
96
    return framePosition
97
}
98
99
/**
100
 * @param {Buffer} encodedSize
101
 * @return {boolean} Return if the header contains a valid encoded size
102
 */
103
 export function isValidEncodedSize(encodedSize) {
104
    // The size must not have the bit 7 set
105
    return ((
106
        encodedSize[0] |
107
        encodedSize[1] |
108
        encodedSize[2] |
109
        encodedSize[3]
110
    ) & 128) === 0
111
}
112
113
/**
114
 * ID3 header size uses only 7 bits of a byte, bit shift is needed
115
 * @param {number} size
116
 * @return {Buffer} Return a Buffer of 4 bytes with the encoded size
117
 */
118
 export function encodeSize(size) {
119
    const byte_3 = size & 0x7F
120
    const byte_2 = (size >> 7) & 0x7F
121
    const byte_1 = (size >> 14) & 0x7F
122
    const byte_0 = (size >> 21) & 0x7F
123
    return Buffer.from([byte_0, byte_1, byte_2, byte_3])
124
}
125
126
/**
127
 * Decode the size encoded in the ID3 header
128
 * @param {Buffer} encodedSize
129
 * @return {number} Return the decoded size
130
 */
131
 export function decodeSize(encodedSize) {
132
    return (
133
        (encodedSize[0] << 21) +
134
        (encodedSize[1] << 14) +
135
        (encodedSize[2] << 7) +
136
        encodedSize[3]
137
    )
138
}
139
140
export function getFrameSize(buffer, decode, ID3Version) {
141
    let decodeBytes
142
    if(ID3Version > 2) {
143
        decodeBytes = [buffer[4], buffer[5], buffer[6], buffer[7]]
144
    } else {
145
        decodeBytes = [buffer[3], buffer[4], buffer[5]]
146
    }
147
    if(decode) {
148
        return this.decodeSize(Buffer.from(decodeBytes))
149
    } else {
0 ignored issues
show
Comprehensibility introduced by
else is not necessary here since all if branches return, consider removing it to reduce nesting and make code more readable.
Loading history...
150
        return Buffer.from(decodeBytes).readUIntBE(0, decodeBytes.length)
151
    }
152
}
153
154
export function parseTagHeaderFlags(header) {
155
    if(!(header instanceof Buffer && header.length >= 10)) {
156
        return {}
157
    }
158
    const version = header[3]
159
    const flagsByte = header[5]
160
    if(version === 3) {
161
        return {
162
            unsynchronisation: !!(flagsByte & 128),
163
            extendedHeader: !!(flagsByte & 64),
164
            experimentalIndicator: !!(flagsByte & 32)
165
        }
166
    }
167
    if(version === 4) {
168
        return {
169
            unsynchronisation: !!(flagsByte & 128),
170
            extendedHeader: !!(flagsByte & 64),
171
            experimentalIndicator: !!(flagsByte & 32),
172
            footerPresent: !!(flagsByte & 16)
173
        }
174
    }
175
    return {}
176
}
177
178
export function processUnsynchronisedBuffer(buffer) {
179
    const newDataArr = []
180
    if(buffer.length > 0) {
181
        newDataArr.push(buffer[0])
182
    }
183
    for(let i = 1; i < buffer.length; i++) {
184
        if(buffer[i - 1] === 0xFF && buffer[i] === 0x00)
185
            continue
0 ignored issues
show
Coding Style Best Practice introduced by
Curly braces around statements make for more readable code and help prevent bugs when you add further statements.

Consider adding curly braces around all statements when they are executed conditionally. This is optional if there is only one statement, but leaving them out can lead to unexpected behaviour if another statement is added later.

Consider:

if (a > 0)
    b = 42;

If you or someone else later decides to put another statement in, only the first statement will be executed.

if (a > 0)
    console.log("a > 0");
    b = 42;

In this case the statement b = 42 will always be executed, while the logging statement will be executed conditionally.

if (a > 0) {
    console.log("a > 0");
    b = 42;
}

ensures that the proper code will be executed conditionally no matter how many statements are added or removed.

Loading history...
186
        newDataArr.push(buffer[i])
187
    }
188
    return Buffer.from(newDataArr)
189
}
190
191
/**
192
 * @param {Buffer} pictureBuffer
193
 * @returns string | null
194
 */
195
export function getPictureMimeTypeFromBuffer(pictureBuffer) {
196
    if (pictureBuffer.length > 3 && pictureBuffer.compare(Buffer.from([0xff, 0xd8, 0xff]), 0, 3, 0, 3) === 0) {
197
        return "image/jpeg"
198
    } else if (pictureBuffer > 8 && pictureBuffer.compare(Buffer.from([0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A]), 0, 8, 0, 8) === 0) {
199
        return "image/png"
200
    } else {
201
        return null
202
    }
203
}
204